﻿//SmartTrader.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Smart
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;
	using System.Net;
	using System.Threading;

	using Ecng.ComponentModel;
	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Interop;
	using Ecng.Serialization;

	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Derivatives;
	using StockSharp.Logging;
	using StockSharp.BusinessEntities;
	using StockSharp.Algo;

	using StClientLib;

	/// <summary>
	/// Реализация интерфейса <see cref="ITrader"/>, предоставляющая шлюз взаимодействия с системой SmartCOM.
	/// </summary>
	public class SmartTrader : BaseTrader
	{
		private sealed class TradesInfo
		{
			private readonly SmartTrader _trader;
			private readonly Action<Trade> _handler;
			private Timer _historyTimeOut;
			private bool _blockRealTime;
			private readonly SynchronizedList<Trade> _realTimeTrades = new SynchronizedList<Trade>();
			private readonly SynchronizedList<Trade> _historyTrades = new SynchronizedList<Trade>();

			public TradesInfo(SmartTrader trader, Action<Trade> handler)
			{
				if (trader == null)
					throw new ArgumentNullException("trader");

				if (handler == null)
					throw new ArgumentNullException("handler");

				_trader = trader;
				_handler = handler;
			}

			public const int MaxTradeCount = 1000000;

			public void CreateTimer()
			{
				_blockRealTime = true;
				_historyTimeOut = ThreadingHelper.Timer(() =>
				{
					ShutdownTimer();

					_trader.ProcessEvents(FlushRealTimeTrades);
				})
				.Interval(TimeSpan.FromSeconds(10));
			}

			private void ShutdownTimer()
			{
				if (_historyTimeOut != null)
				{
					_historyTimeOut.Dispose();
					_historyTimeOut = null;
				}
			}

			private void FlushRealTimeTrades()
			{
				FlushTrades(_realTimeTrades, () => _blockRealTime = false);
			}

			public void FlushHistoryTrades()
			{
				FlushTrades(_historyTrades, () => { });
				FlushRealTimeTrades();
			}

			private void FlushTrades(SynchronizedList<Trade> trades, Action action)
			{
				if (trades == null)
					throw new ArgumentNullException("trades");

				trades.SyncDo(c =>
				{
					c.OrderBy(t => t.Id).ForEach(_handler);
					c.Clear();
					action();
				});
			}

			public void AddRealTimeTrade(Trade trade)
			{
				var canProcess = _realTimeTrades.SyncGet(c =>
				{
					if (_blockRealTime)
						c.Add(trade);

					return !_blockRealTime;
				});

				if (canProcess)
					_handler(trade);
			}

			public void AddHistoryTrade(Trade trade)
			{
				ShutdownTimer();
				_historyTrades.Add(trade);
			}
		}

		//private const string _ordersXmlFileName = "orders.xml";

		private readonly SynchronizedMultiDictionary<string, Order> _smartIdOrders = new SynchronizedMultiDictionary<string, Order>();
		private readonly SynchronizedDictionary<Tuple<Security, SmartTimeFrames>, DateTime> _realTimeTokens = new SynchronizedDictionary<Tuple<Security, SmartTimeFrames>, DateTime>();
		private readonly SynchronizedDictionary<Security, MarketDepth> _tempDepths = new SynchronizedDictionary<Security, MarketDepth>();
		private readonly SynchronizedDictionary<Tuple<Security, SmartTimeFrames>, SynchronizedList<TimeFrameCandle>> _historyCandles = new SynchronizedDictionary<Tuple<Security, SmartTimeFrames>, SynchronizedList<TimeFrameCandle>>();
		private readonly SynchronizedDictionary<Security, TradesInfo> _securityTradeInfo = new SynchronizedDictionary<Security, TradesInfo>();

		private bool _newSecuritiesRaised;

		//private bool _isNeedAutoSave;
		//private Timer _autoSaveTimer;
		//private readonly object _autoSaveLock = new object();

		private readonly Timer _realTimeCandlesTimer;

		private bool _synchronizeOrderEvents;
		private readonly SynchronizedDictionary<string, Action> _suspendedOrderEvents = new SynchronizedDictionary<string, Action>();

		/// <summary>
		/// Создать <see cref="SmartTrader"/>.
		/// </summary>
		public SmartTrader()
			: base(Platforms.x86)
		{
			Wrapper = new SmartComWrapper(ProcessEvents, RaiseProcessDataError);

			Wrapper.NewSecurity += OnNewSecurity;
			Wrapper.SecurityChanged += OnSecurityChanged;
			Wrapper.NewPortfolio += OnNewPortfolio;
			Wrapper.PortfolioChanged += OnPortfolioChanged;
			Wrapper.PositionChanged += OnPositionChanged;
			Wrapper.QuoteChanged += OnQuoteChanged;
			Wrapper.NewTrade += OnNewTrade;
			Wrapper.NewMyTrade += OnNewMyTrade;
			Wrapper.NewHistoryTrade += OnNewHistoryTrade;
			Wrapper.NewBar += OnNewBar;
			Wrapper.NewOrder += OnNewOrder;
			Wrapper.OrderFailed += OnOrderFailed;
			Wrapper.OrderCancelFailed += OnOrderCancelFailed;
			Wrapper.OrderChanged += OnOrderChanged;
			Wrapper.Connected += OnConnected;
			Wrapper.Disconnected += OnDisconnected;

			Wrapper.OrderReRegistered += OnOrderReRegistered;
			Wrapper.OrderReRegisterFailed += OnOrderReRegisterFailed;

			Address = SmartAddresses.Major;

			ExchangeBoardCodes = new Dictionary<string, ExchangeBoard>
			{
			    { "EQ", ExchangeBoard.Micex },
			    { "FOB", ExchangeBoard.Micex },
			    { "RTS_FUT", ExchangeBoard.Forts },
			};

			SecurityClassInfo.Add("OPT", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Option, ExchangeBoard.Forts));
			SecurityClassInfo.Add("OPTM", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Option, ExchangeBoard.Forts));
			SecurityClassInfo.Add("FUT", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Future, ExchangeBoard.Forts));

			//IsAutoSaveOrders = false;

			base.NewPortfolios += OnNewPortfolios;

			_realTimeCandlesTimer = ThreadingHelper.Timer(() =>
			{
				if (!IsConnected)
					return;

				_realTimeTokens.SyncDo(d =>
				{
					foreach (var pair in d.ToArray())
					{
						var token = pair.Key;
						var tf = token.Item2;

						var beginTime = pair.Value;
						var finishTime = beginTime + (TimeSpan)tf;

						var security = token.Item1;

						if (finishTime < security.GetMarketTime())
						{
							RegisterHistoryCandles(security, tf, new Range<DateTime>(beginTime, finishTime));
							d[token] = finishTime;
						}
					}
				});
			})
			.Interval(TimeSpan.FromSeconds(1));
		}

		/// <summary>
		/// Создать <see cref="SmartTrader"/>.
		/// </summary>
		/// <param name="login">Логин.</param>
		/// <param name="password">Пароль.</param>
		public SmartTrader(string login, string password)
			: this(login, password, SmartAddresses.Major)
		{
		}

		/// <summary>
		/// Создать <see cref="SmartTrader"/>.
		/// </summary>
		/// <param name="login">Логин.</param>
		/// <param name="password">Пароль.</param>
		/// <param name="address">IP адрес и порт сервера.</param>
		public SmartTrader(string login, string password, EndPoint address)
			: this()
		{
			Login = login;
			Password = password;
			Address = address;
		}

		private void OnOrderReRegisterFailed(string smartOrderId)
		{
			this.AddErrorLog(() => "MoveFailed, smartOrderId={0}".Put(smartOrderId));

			var order = GetOrderBySmartId(smartOrderId);
			RaiseOrderFailed(order, new InvalidOperationException("Ошибка при перемещении заявки с номером транзакции {0}.".Put(order.TransactionId)));
		}

		private void OnOrderReRegistered(string smartOrderId)
		{
			this.AddInfoLog(() => "MoveSucc, smartOrderId={0}".Put(smartOrderId));
		}

		private void OnNewPortfolios(IEnumerable<Portfolio> portfolios)
		{
			portfolios.ForEach(RegisterPortfolio);
		}

		/// <summary>
		/// Обертка над SmartCOM API.
		/// </summary>
		public SmartComWrapper Wrapper { get; private set; }

		/// <summary>
		/// Адрес сервера. По-умолчанию равно <see cref="SmartAddresses.Major"/>.
		/// </summary>
		public EndPoint Address
		{
			get { return Wrapper.Address; }
			set { Wrapper.Address = value; }
		}

		/// <summary>
		/// Текстовое описание шлюза.
		/// </summary>
		public override string DisplayName
		{
			get { return Address.ToString(); }
		}

		/// <summary>
		/// Логин.
		/// </summary>
		public string Login
		{
			get { return Wrapper.Login; }
			set { Wrapper.Login = value; }
		}

		/// <summary>
		/// Пароль.
		/// </summary>
		public string Password
		{
			get { return Wrapper.Password; }
			set { Wrapper.Password = value; }
		}

		/// <summary>
		/// Проверить соединение.
		/// </summary>
		public override bool IsConnected
		{
			get { return Wrapper.IsConnected; }
		}

		/// <summary>
		/// Поддерживается ли перерегистрация заявок через метод <see cref="ITrader.ReRegisterOrder(StockSharp.BusinessEntities.Order,StockSharp.BusinessEntities.Order)"/>
		/// в виде одной транзакции. По-умолчанию включено.
		/// </summary>
		public override bool IsSupportAtomicReRegister
		{
			get
			{
				// http://stocksharp.com/forum/yaf_postsm20807_MarketQuotingStrategy---obiem-nie-mozhiet-byt--nulievym.aspx
				return false;
			}
		}

		///// <summary>
		///// Автоматическое сохранение зарегистрированных заявок в файле orders.xml.
		///// </summary>
		///// <remarks>
		///// Необходимо для предотвращения потери заявок в случае нестабильной работы SmartCOM.
		///// </remarks>
		//public bool IsAutoSaveOrders
		//{
		//    get
		//    {
		//        return _autoSaveTimer != null;
		//    }
		//    set
		//    {
		//        if (value == IsAutoSaveOrders)
		//            return;

		//        if (value)
		//        {
		//            _autoSaveTimer = ThreadingHelper.Timer(() =>
		//            {
		//                lock (_autoSaveLock)
		//                {
		//                    if (_isNeedAutoSave && _newSecuritiesRaised)
		//                    {
		//                        _isNeedAutoSave = false;

		//                        CultureInfo.InvariantCulture.DoInCulture(() =>
		//                        {
		//                            new XElement("orders", Orders.OrderBy(o => o.TransactionId).Select(o =>
		//                                new XElement("order",
		//                                    new XElement("id", o.Id),
		//                                    new XElement("transactionId", o.TransactionId),
		//                                    new XElement("direction", o.Direction),
		//                                    new XElement("portfolio", o.Portfolio.Name),
		//                                    new XElement("time", o.Time),
		//                                    new XElement("price", o.Price.ToString()),
		//                                    new XElement("state", o.State),
		//                                    new XElement("balance", o.Balance),
		//                                    new XElement("volume", o.Volume),
		//                                    new XElement("security", o.Security.Id),
		//                                    new XElement("type", o.Type),
		//                                    new XElement("isOneDay", o.ExpiryDate != null && o.ExpiryDate.Value == DateTime.Today),
		//                                    new XElement("condition", o.StopCondition != null ? new[]
		//                                    {
		//                                        new XElement("stopPrice", ((SmartStopCondition)o.StopCondition).StopPrice.ToString())
		//                                    } : new XElement[0]),
		//                                    new XElement("latency", o.Latency),
		//                                    new XElement("status", o.Status),
		//                                    new XElement("smartOrderId", o.GetSmartId() ?? string.Empty),
		//                                    new XElement("comment", o.Comment),
		//                                    new XElement("cancelTime", o.CancelTime),
		//                                    new XElement("derivedOrderId", o.DerivedOrder != null ? o.DerivedOrder.Id.ToString() : string.Empty)
		//                                ))).Save(_ordersXmlFileName);
		//                        });
		//                    }
		//                }
		//            })
		//            .Interval(TimeSpan.FromSeconds(10));
		//        }
		//        else
		//        {
		//            _autoSaveTimer.Dispose();
		//            _autoSaveTimer = null;
		//        }
		//    }
		//}

		/// <summary>
		/// Получать ли все тиковые сделки с начала сессии при вызове метода <see cref="ITrader.RegisterTrades"/>
		/// или только сделки с момента вызова данного метода.
		/// </summary>
		public bool TradesFromSessionStart { get; set; }

		/// <summary>
		/// Ассоциация площадок и их кодами, для заполнения <see cref="Portfolio.ExchangeBoard"/>.
		/// </summary>
		public IDictionary<string, ExchangeBoard> ExchangeBoardCodes { get; private set; }

		/// <summary>
		/// Событие появления исторических тайм-фрейм свечек от сервера SmartCOM, запрос на получение которых был отправлен через метод
		/// <see cref="RegisterHistoryCandles(StockSharp.BusinessEntities.Security,StockSharp.Smart.SmartTimeFrames,System.DateTime,int,StockSharp.Smart.SmartHistoryDirections)"/>.
		/// </summary>
		public event Action<IEnumerable<TimeFrameCandle>> NewHistoryCandles;

		/// <summary>
		/// Начать получать непрерывно исторические тайм-фрейм свечки от сервера SmartCOM, а так же свечки реального времени.
		/// </summary>
		/// <remarks>
		/// После окончания получения исторических свечек возвращаются свечки реального времени.
		/// </remarks>
		/// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		public void RegisterHistoryRealTimeCandles(Security security, SmartTimeFrames timeFrame)
		{
			RegisterHistoryRealTimeCandles(security, timeFrame, security.GetMarketTime());
		}

		/// <summary>
		/// Начать получать непрерывно исторические тайм-фрейм свечки от сервера SmartCOM, а так же свечки реального времени.
		/// </summary>
		/// <remarks>
		/// После окончания получения исторических свечек возвращаются свечки реального времени.
		/// </remarks>
		/// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="from">Временная точка отсчета, с которой необходимо начать получать свечки.</param>
		public void RegisterHistoryRealTimeCandles(Security security, SmartTimeFrames timeFrame, DateTime from)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			var to = ((TimeSpan)timeFrame).GetCandleBounds(security).Min;

			if (from >= (to - (TimeSpan)timeFrame))
				from = to - (TimeSpan)timeFrame;

			RegisterHistoryCandles(security, timeFrame, new Range<DateTime>(from, to));
			_realTimeTokens.Add(timeFrame.GetToken(security), to);
		}

		/// <summary>
		/// Отменить получение исторических свечек, зарегистрированное через
		/// <see cref="RegisterHistoryRealTimeCandles(StockSharp.BusinessEntities.Security,StockSharp.Smart.SmartTimeFrames)"/>.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		public void UnRegisterHistoryRealTimeCandles(Security security, SmartTimeFrames timeFrame)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			_realTimeTokens.Remove(timeFrame.GetToken(security));
		}

		/// <summary>
		/// Начать получать исторические тайм-фрейм свечки от сервера SmartCOM.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="range">Диапазон времени, для которого нужно получить свечки.</param>
		public void RegisterHistoryCandles(Security security, SmartTimeFrames timeFrame, Range<DateTime> range)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			if (range == null)
				throw new ArgumentNullException("range");

			RegisterHistoryCandles(security, timeFrame, range.Max, (int)security.GetTimeFrameCount(range, (TimeSpan)timeFrame), SmartHistoryDirections.Backward);
		}

		/// <summary>
		/// Начать получать исторические тайм-фрейм свечки от сервера SmartCOM.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="from">Временная точка отсчета.</param>
		/// <param name="count">Количество свечек.</param>
		/// <param name="direction">Направление поиска относительно параметра from. Если значение равно <see cref="SmartHistoryDirections.Forward"/>,
		/// то данные ищутся от from в сторону увеличения времени. Если значение равно <see cref="SmartHistoryDirections.Backward"/>, то свечки
		/// ищутся до from в сторону уменьшения времени.</param>
		public void RegisterHistoryCandles(Security security, SmartTimeFrames timeFrame, DateTime from, int count, SmartHistoryDirections direction)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (timeFrame == null)
				throw new ArgumentNullException("timeFrame");

			if (count < 0)
				throw new ArgumentOutOfRangeException("count");

			// http://stocksharp.com/forum/yaf_postst658_provierka-na-vriemia-birzhi-pri-zaghruzkie-istorii.aspx
			//if (from > MarketTime)
			//	throw new ArgumentOutOfRangeException("from", from, "Параметр from не может быть больше текущего времени биржи.");

			//var token = _candleTokens.SafeAdd(new Tuple<Security, SmartTimeFrames>(security, timeFrame), key => new CandleToken(security, timeFrame));

			if (count > 0)
			{
				var secId = security.GetSmartId();

				//if (timeFrame == SmartTimeFrames.Tick)
				//{
				//    if (direction == SmartHistoryDirections.Forward)
				//        throw new ArgumentException("Направление поиска при тиковых сделках может быть только SmartHistoryDirections.Forward.", "direction");

				//    Wrapper.RegisterHistoryTrades(secId, from, count);
				//}
				//else
				{
					if (direction == SmartHistoryDirections.Forward)
						count = -count;

					Wrapper.RegisterHistoryBars(secId, timeFrame, from, count);
				}
			}

			//return token;
		}

		/// <summary>
		/// Остановить процесс SmartCom2.exe.
		/// </summary>
		public static void KillSmartComProcess()
		{
			var process = Process.GetProcesses().FirstOrDefault(p => p.ProcessName == "SmartCom2");

			if (process != null)
			{
				process.Kill();
				TimeSpan.FromSeconds(3).Sleep();
			}
		}

		/// <summary>
		/// Подключиться к торговой системе.
		/// </summary>
		protected override void OnConnect()
		{
			try
			{
				KillSmartComProcess();
			}
			catch (Exception ex)
			{
				RaiseProcessDataError(ex);
			}

			Wrapper.Connect();
		}

		/// <summary>
		/// Отключиться от торговой системы.
		/// </summary>
		protected override void OnDisconnect()
		{
			if (Wrapper.IsConnected)
				StopExport();

			Wrapper.Disconnect();
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу (получение портфелей, инструментов, заявок и т.д.).
		/// </summary>
		public override void StartExport()
		{
			Wrapper.StartExport();
			base.StartExport();
		}

		/// <summary>
		/// Зарегистрировать заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		protected override void OnRegisterOrder(Order order)
		{
			_synchronizeOrderEvents = true;

			if (order.ExecutionCondition != OrderExecutionConditions.PutInQueue)
				throw new ArgumentException("SmartCOM не поддерживает условие исполнения {0}.".Put(order.ExecutionCondition));

			var condition = (SmartOrderCondition)order.Condition;

			Wrapper.RegisterOrder(
				order.Portfolio.Name, order.Security.GetSmartId(), this.GetSmartAction(order), order.GetSmartOrderType(),
				order.ExpiryDate != null && order.ExpiryDate.Value == DateTime.Today, (double)order.Price, (int)order.Volume,
				condition != null ? (double)condition.StopPrice : 0, (int)order.TransactionId);
		}

		/// <summary>
		/// Перерегистрировать заявку на бирже.
		/// </summary>
		/// <param name="oldOrder">Старая заявка, которую нужно перерегистрировать.</param>
		/// <param name="newOrder">Информация о новой заявке.</param>
		protected override void OnReRegisterOrder(Order oldOrder, Order newOrder)
		{
			if (oldOrder.Security.ExchangeBoard == ExchangeBoard.Forts
				&& oldOrder.Balance == newOrder.Volume) // http://www.itinvest.ru/forum/index.php?showtopic=63720&view=findpost&p=262059
			{
				InitNewOrder(newOrder);

				var smartId = GetSmartId(oldOrder);

				newOrder.Portfolio = oldOrder.Portfolio;

				_smartIdOrders.SyncDo(d =>
				{
					AssociateSmartOrderId(newOrder, smartId);

					// если заявка была первоначально зарегистрирована не через SmartTrader
					if (!d.Contains(smartId, oldOrder))
						d.Add(smartId, oldOrder);
				});

				this.AddOrderInfoLog(newOrder, "ReRegisterOrder", () => "ReRegisterOrder(FORTS), old tid={0}, id={1}, sid={2}".Put(oldOrder.TransactionId, oldOrder.Id, oldOrder.GetSmartId()));
				
				Wrapper.ReRegisterOrder(newOrder.Portfolio.Name, (double)newOrder.Price, smartId);
			}
			else
				base.OnReRegisterOrder(oldOrder, newOrder);
		}

		/// <summary>
		/// Отменить заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменять.</param>
		protected override void OnCancelOrder(Order order)
		{
			Wrapper.CancelOrder(order.Portfolio.Name, order.Security.GetSmartId(), GetSmartId(order));
		}

		/// <summary>
		/// Отменить группу заявок на бирже по фильтру.
		/// </summary>
		/// <param name="isStopOrder">True, если нужно отменить только стоп-заявки, false - если только обычный и null - если оба типа.</param>
		/// <param name="portfolio">Портфель. Если значение равно null, то портфель не попадает в фильтр снятия заявок.</param>
		/// <param name="direction">Направление заявки. Если значение равно null, то направление не попадает в фильтр снятия заявок.</param>
		/// <param name="classCode">Код класса. Если переданная строка пустая, то код не попадает в фильтр снятия заявок.</param>
		/// <param name="security">Инструмент. Если значение равно null, то инструмент не попадает в фильтр снятия заявок.</param>
		public override void CancelOrders(bool? isStopOrder = null, Portfolio portfolio = null, OrderDirections? direction = null, string classCode = null, Security security = null)
		{
			if (isStopOrder == null && portfolio == null && direction == null && classCode.IsEmpty() && security == null)
				Wrapper.CancelAllOrders();
			else
				base.CancelOrders(isStopOrder, portfolio, direction, classCode, security);
		}

		/// <summary>
		/// Начать получать новую информацию (например, <see cref="Security.LastTrade"/> или <see cref="Security.BestBid"/>) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать новую информацию.</param>
		protected override void OnRegisterSecurity(Security security)
		{
			Wrapper.RegisterSecurity(security.GetSmartId());
		}

		/// <summary>
		/// Остановить получение новой информации.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение новой информации.</param>
		protected override void OnUnRegisterSecurity(Security security)
		{
			Wrapper.UnRegisterSecurity(security.GetSmartId());
		}

		/// <summary>
		/// Начать получать котировки (стакан) по инструменту.
		/// Значение котировок можно получить через метод <see cref="ITrader.GetMarketDepth"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать котировки.</param>
		protected override void OnRegisterMarketDepth(Security security)
		{
			Wrapper.RegisterQuotes(security.GetSmartId());
		}

		/// <summary>
		/// Остановить получение котировок по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение котировок.</param>
		protected override void OnUnRegisterMarketDepth(Security security)
		{
			Wrapper.UnRegisterQuotes(security.GetSmartId());
		}

		/// <summary>
		/// Начать получать сделки (тиковые данные) по инструменту. Новые сделки будут приходить через событие <see cref="ITrader.NewTrades"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать сделки.</param>
		protected override void OnRegisterTrades(Security security)
		{
			RegisterTrades(security, TradesFromSessionStart);
		}

		/// <summary>
		/// Начать получать сделки (тиковые данные) по инструменту. Новые сделки будут приходить через событие <see cref="ITrader.NewTrades"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать сделки.</param>
		/// <param name="fromSessionStart">Получать ли сделки с начала сессии.</param>
		public void RegisterTrades(Security security, bool fromSessionStart)
		{
			var info = _securityTradeInfo.SafeAdd(security, key => new TradesInfo(this, t => GetTrade(security, t.Id, id => t)));

			if (fromSessionStart)
			{
				info.CreateTimer();
				Wrapper.RegisterHistoryTrades(security.GetSmartId(), DateTime.Today, TradesInfo.MaxTradeCount);
			}

			Wrapper.RegisterTrades(security.GetSmartId());
		}

		/// <summary>
		/// Остановить получение сделок (тиковые данные) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение сделок.</param>
		protected override void OnUnRegisterTrades(Security security)
		{
			Wrapper.UnRegisterTrades(security.GetSmartId());
		}

		/// <summary>
		/// Начать получать новую информацию по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому необходимо начать получать новую информацию.</param>
		protected override void OnRegisterPortfolio(Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			Wrapper.RegisterPortfolio(portfolio.Name);
		}

		/// <summary>
		/// Остановить получение новой информации по портфелю.
		/// </summary>
		/// <param name="portfolio">Портфель, по которому необходимо остановить получение новой информации.</param>
		protected override void OnUnRegisterPortfolio(Portfolio portfolio)
		{
			if (portfolio == null)
				throw new ArgumentNullException("portfolio");

			Wrapper.UnRegisterPortfolio(portfolio.Name);
		}

		/// <summary>
		/// Поддерживает ли шлюз инструмент.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>True, если шлюз поддерживает инструмент, иначе, false.</returns>
		public override bool IsSupport(Security security)
		{
			return !security.GetSmartId().IsEmpty();
		}

		private void OnNewPortfolio(int row, int rowCount, string name, string exchange, StPortfolioStatus status)
		{
			GetPortfolio(name, portfolio =>
			{
				portfolio.ExchangeBoard = ExchangeBoardCodes.TryGetValue(exchange);
				portfolio.SetExchange(exchange);
				portfolio.SetSmartStatus(status);
				portfolio.RaiseExtensionInfoChanged();
				return true;
			});
		}

		private void OnPortfolioChanged(string name, decimal cash, decimal leverage, decimal commission, decimal saldo)
		{
			GetPortfolio(name, portfolio =>
			{
				using (portfolio.BeginUpdate())
				{
					portfolio.Leverage = leverage;
					portfolio.Commission = commission;
					portfolio.CurrentValue = cash;
					portfolio.BeginValue = cash;
					portfolio.VariationMargin = 0; // Не транслируется. Saldo - сколько текущих денег осталось под новые позы
				}
				return true;
			});
		}

		private void OnPositionChanged(string portfolioName, string securityId, decimal avPrice, decimal amount, decimal planned)
		{
			ProcessSecurityAction(null, securityId, security => UpdatePosition(GetPortfolio(portfolioName), security, position =>
			{
				using (position.BeginUpdate())
				{
					position.CurrentValue = amount;
					position.BlockedValue = planned;
				}
				return true;
			}));
		}

		private Trade CreateTrade(Security security, DateTime time, decimal price, decimal volume, long tradeId, StOrder_Action action)
		{
			var trade = EntityFactory.CreateTrade(security, tradeId);

			trade.Price = price;
			trade.Volume = volume;
			trade.OrderDirection = action.ToOrderDirection();
			trade.Time = time;

			trade.InitLatency();

			return trade;
		}

		private void OnNewHistoryTrade(int row, int rowCount, string securityId, DateTime time, decimal price, decimal volume, long tradeId, StOrder_Action action)
		{
			//Debug.WriteLine("OnNewHistoryTrade {0} {1} {2} {3}".Put(row, rowCount, time, price));

			ProcessSecurityAction(null, securityId, security =>
			{
				var info = _securityTradeInfo[security];

				info.AddHistoryTrade(CreateTrade(security, time, price, volume, tradeId, action));

				if (row == (rowCount - 1))
					info.FlushHistoryTrades();
			});
		}

		private void OnNewBar(int row, int rowCount, string securityId, SmartTimeFrames timeFrame, DateTime time, decimal open, decimal high, decimal low, decimal close, decimal volume, decimal openInt)
		{
			ProcessSecurityAction(null, securityId, security =>
			{
				var candle = new TimeFrameCandle
				{
					Security = security,
					OpenPrice = open,
					HighPrice = high,
					LowPrice = low,
					ClosePrice = close,
					TotalVolume = volume,
					TimeFrame = (TimeSpan)timeFrame,
					OpenTime = time - (TimeSpan)timeFrame,
					CloseTime = time,
					State = CandleStates.Finished,
				};

				var candles = _historyCandles.SafeAdd(timeFrame.GetToken(security)).SyncGet(c =>
				{
					c.Add(candle);

					return row == (rowCount - 1)
						? c.CopyAndClear().OrderBy(t => t.OpenTime)
						: Enumerable.Empty<TimeFrameCandle>();
				});

				if (!candles.IsEmpty())
					NewHistoryCandles.SafeInvoke(candles);
			});
		}

		private void OnNewMyTrade(string portfolio, string securityId, long orderId, decimal price, decimal volume, DateTime time, long tradeId)
		{
			this.AddInfoLog(() => "SmartTrader.AddTrade: tradeId {0} orderId {1} price {2} volume {3} time {4} security {5}".Put(tradeId, orderId, price, volume, time, securityId));

			ProcessSecurityAction(null, securityId, security => AddMyTrade(security, orderId, 0, tradeId,
				id => CreateTrade(security, time, price, volume, id, 0), t => { }));
		}

		private void OnNewTrade(string securityId, DateTime time, decimal price, decimal volume, long tradeId, StOrder_Action action)
		{
			ProcessSecurityAction(null, securityId, security =>
			{
				var info = _securityTradeInfo[security];
				info.AddRealTimeTrade(CreateTrade(security, time, price, volume, tradeId, action));
			});
		}

		private void OnNewSecurity(int row, int rowCount, string smartId, string name, string secCode, string secClass, int decimals, int lotSize,
			decimal minStepPrice, decimal minStepSize, string isin, string board, DateTime? expiryDate, decimal daysBeforeExpiry, decimal strike)
		{

			//AMU: заглушка. 11.01.2013 обнаружил, что через SmartCom стали приходить инструменты (класс EQBR и FISS) с пустым secCode - "longName" в понятии АйтиИнвеста
			if (secCode.IsEmpty())
			{
				secCode = smartId;
			}
			var securityId = CreateSecurityId(secCode, secClass);

			AddNativeSecurityId(GetSecurity(securityId, security =>
			{
				security.Code = secCode;
				security.Class = secClass;
				security.MinStepSize = minStepSize;
				security.MinLotSize = lotSize;
				security.Name = name;
				security.ShortName = name;
				security.MinStepPrice = minStepPrice;
				security.ExpiryDate = expiryDate;

				if (secClass.CompareIgnoreCase("IDX"))
				{
					security.Type = SecurityTypes.Index;

					switch (board)
					{
						case "RUSIDX":
							security.ExchangeBoard = secCode.ContainsIgnoreCase("MICEX") || secCode.ContainsIgnoreCase("MCX") ? ExchangeBoard.Micex : ExchangeBoard.Forts;
							break;
						default:
							security.ExchangeBoard = ExchangeBoard.Test;
							break;
					}
				}
				else
				{
					var info = GetSecurityClassInfo(secClass);

					security.Type = info.First;
					security.ExchangeBoard = info.Second;

					// http://stocksharp.com/forum/yaf_postsm16847_Vopros-po-vystavlieniiu-zaiavok.aspx#post16847
					if (security.ExchangeBoard == ExchangeBoard.Micex
						&& /* проверяем, что не началась ли трансляция правильных дробных шагов */security.MinStepSize == (int)security.MinStepSize)
					{
						// http://stocksharp.com/forum/yaf_postsm21245_Sokhranieniie-stakanov-po-GAZP-EQNE.aspx#post21245
						security.MinStepSize = 1m / 10m.Pow(security.MinStepSize);
					}
				}

				if (security.Type == SecurityTypes.Option)
				{
					var optionInfo = name.GetOptionInfo();
					if (optionInfo != null)
					{
						// http://stocksharp.com/forum/yaf_postst1355_Exception-Change-Set-11052.aspx
						if (!secCode.IsEmpty())
						{
							var futureInfo = optionInfo.UnderlyingSecurityId.GetFutureInfo(secCode);
							if (futureInfo != null)
								security.UnderlyingSecurityId = CreateSecurityId(futureInfo.Code, secClass);
						}

						security.ExpiryDate = optionInfo.ExpiryDate;
						security.OptionType = optionInfo.OptionType;
						security.Strike = optionInfo.Strike;
					}
				}

				security.ExternalId.Isin = isin ?? string.Empty;
				security.SetSmartId(smartId);
				security.SetDaysBeforeExpiry(daysBeforeExpiry);
				security.SetBoard(board);

				security.RaiseExtensionInfoChanged();

				return true;
			}), smartId);


			if (!_newSecuritiesRaised)
			{
				//Debug.WriteLine("{0} - {1}".Put(row, rowCount));

				if ((row + 1) == rowCount)
				{
					Debug.WriteLine("SmartTrader RaiseNewSecurities");

					_newSecuritiesRaised = true;
					//RaiseNewSecurities(Securities);

					//if (IsAutoSaveOrders)
					//{
					//    lock (_autoSaveLock)
					//    {
					//        if (!File.Exists(_ordersXmlFileName))
					//            return;

					//        CultureInfo.InvariantCulture.DoInCulture(() =>
					//        {
					//            var doc = XDocument.Load(_ordersXmlFileName);
					//            if (doc.Root != null)
					//            {
					//                doc.Root.Elements().Select(e =>
					//                {
					//                    var order = EntityFactory.CreateOrder(GetXmlValue<long>(e, "id"));

					//                    order.TransactionId = GetXmlValue<long>(e, "transactionId");
					//                    order.Direction = GetXmlValue<OrderDirections>(e, "direction");
					//                    order.Portfolio = GetPortfolio(GetXmlValue<string>(e, "portfolio"));
					//                    order.Time = GetXmlValue<DateTime>(e, "time");
					//                    order.Price = GetXmlValue<decimal>(e, "price");
					//                    order.State = GetXmlValue<OrderStates>(e, "state");
					//                    order.Balance = GetXmlValue<int>(e, "balance");
					//                    order.Volume = GetXmlValue<int>(e, "volume");
					//                    order.Security = GetSecurity(GetXmlValue<string>(e, "security"));
					//                    order.Type = GetXmlValue<OrderTypes>(e, "type");
					//                    order.Latency = GetXmlValue<TimeSpan>(e, "latency");
					//                    order.Status = GetXmlValue<OrderStatus?>(e, "status");

					//                    if (order.ExtensionInfo == null)
					//                        order.ExtensionInfo = new Dictionary<object, object>();

					//                    order.SetSmartId(GetXmlValue<string>(e, "smartOrderId"));

					//                    order.Comment = GetXmlValue<string>(e, "comment");
					//                    order.CancelTime = GetXmlValue<DateTime?>(e, "cancelTime");
					//                    order.ExpiryDate = GetXmlValue<bool>(e, "isOneDay") ? DateTime.Today : DateTime.MaxValue;

					//                    if (order.Type == OrderTypes.Conditional)
					//                    {
					//                        var conditionElem = e.Element("condition");

					//                        if (conditionElem == null)
					//                            throw new InvalidOperationException("Элемент 'condition' для условной заявки не найден.");

					//                        order.StopCondition = new SmartStopCondition
					//                        {
					//                            StopPrice = GetXmlValue<decimal>(conditionElem, "stopPrice"),
					//                        };

					//                        var derivedOrderId = GetXmlValue<long?>(e, "derivedOrderId");

					//                        if (derivedOrderId != null)
					//                            AddDerivedOrder(order.Security, (long)derivedOrderId, order, (s, o) => s.DerivedOrder = o);
					//                    }

					//                    return order;
					//                }).ForEach(o => GetOrder(o.Security, o.Id, key => o, o1 => { }));
					//            }
					//        });
					//    }
					//}
				}
			}
		}

		private void OnSecurityChanged(string smartId, Trade lastTrade, decimal open, decimal high, decimal low, decimal close, decimal volume, Quote bid, Quote ask,
			decimal openInt, Tuple<decimal, decimal> goBuySell, Tuple<decimal, decimal> goBase, Tuple<decimal, decimal> limits, int tradingStatus, Tuple<decimal, decimal> volatTheorPrice)
		{
			ProcessSecurityAction(null, smartId, security =>
			{
				using (security.BeginUpdate())
				{
					if (lastTrade.Price != 0)
					{
						//lastTrade.Security = security;

						security.LastTrade = EntityFactory.CreateTrade(security, 0);
						security.LastTrade.Time = lastTrade.Time;
						security.LastTrade.Price = lastTrade.Price;
						security.LastTrade.Volume = lastTrade.Volume;

						security.LastTrade.InitLatency();
					}
					else
						security.LastChangeTime = security.GetMarketTime();

					if (bid.Price != 0)
						security.BestBid = new Quote { Price = bid.Price, Security = security, OrderDirection = OrderDirections.Buy };

					if (ask.Price != 0)
						security.BestAsk = new Quote { Price = ask.Price, Security = security, OrderDirection = OrderDirections.Sell };

					var depth = GetMarketDepth(security);

					// событие SecurityChanged у SmartCOM транслирует только цены для лучших котировок
					if (security.BestBid != null && depth.BestBid != null && security.BestBid.Price == depth.BestBid.Price)
					{
						security.BestBid.Volume = depth.BestBid.Volume;
						security.BestBid = security.BestBid;
					}

					if (security.BestAsk != null && depth.BestAsk != null && security.BestAsk.Price == depth.BestAsk.Price)
					{
						security.BestAsk.Volume = depth.BestAsk.Volume;
						security.BestAsk = security.BestAsk;
					}

					security.OpenPrice = open;
					security.LowPrice = low;
					security.HighPrice = high;
					security.ClosePrice = close;

					security.MinPrice = limits.Item1;
					security.MaxPrice = limits.Item2;

					security.MarginBuy = goBuySell.Item1;
					security.MarginSell = goBuySell.Item2;
					security.OpenInterest = openInt;

					security.ImpliedVolatility = volatTheorPrice.Item1;
					security.TheorPrice = volatTheorPrice.Item2;

					security.BidsVolume = bid.Volume;
					security.AsksVolume = ask.Volume;
					security.SetTotalVolume(volume);
					security.SetOptionsMargin(goBase.Item1);
					security.SetOptionsSyntheticMargin(goBase.Item2);

					security.RaiseExtensionInfoChanged();

					security.State = tradingStatus == 0 ? SecurityStates.Trading : SecurityStates.Stoped;
				}

				RaiseSecurityChanged(security);
			}, true);
		}

		private void OnNewOrder(int cookie, string smartOrderId)
		{
			var order = GetOrderByTransactionId(cookie) ?? GetStopOrderByTransactionId(cookie);

			using (order.BeginUpdate())
			{
				if (order.State == OrderStates.None)
					order.State = OrderStates.Active;

				AssociateSmartOrderId(order, smartOrderId);
			}

			var evt = _suspendedOrderEvents.TryGetValue(smartOrderId);

			if (evt != null)
				evt();
		}

		private void OnOrderFailed(int cookie, string smartOrderId, string reason)
		{
			var order = GetOrderByTransactionId(cookie) ?? GetStopOrderByTransactionId(cookie);

			this.AddOrderErrorLog(order, "OnOrderFailed", () => "sid={0}, reason={1}".Put(smartOrderId, reason));
			
			if (order.State == OrderStates.Failed)
				return;

			order.SetSmartId(smartOrderId);
			order.RaiseExtensionInfoChanged();

			RaiseOrderFailed(order, new InvalidOperationException(reason ?? "Неизвестная причина ошибки регистрации заявки с номером транзакции {0}.".Put(order.TransactionId)));
		}

		private void OnOrderCancelFailed(string smartOrderId)
		{
			var order = GetOrderBySmartId(smartOrderId);

			this.AddOrderErrorLog(order, "CancelFailed", () => "sid=" + smartOrderId);

			RaiseOrderFailed(order, new InvalidOperationException("Не удалось снять заявку с номером {0} и транзакцией {1}.".Put(order.Id, order.TransactionId)));
		}

		private void OnOrderChanged(string portfolio, string securityId, StOrder_State state, StOrder_Action action, StOrder_Type type, bool isOneDay,
			decimal price, int volume, decimal stop, int balance, DateTime time, string smartOrderId, long orderId, int status, int transactionId)
		{
			var direction = action.ToOrderDirection();

			if (direction == null)
				throw new InvalidOperationException("Неправильное направление заявки {0}.".Put(action));
			
			//Debug.WriteLine("SmartTrader.UpdateOrder: id {0} smartId {1} type {2} direction {3} price {4} volume {5} balance {6} time {7} security {8} state {9}".Put(orderId, smartOrderId, type, direction, price, volume, balance, time, securityId, state));
			this.AddInfoLog(() => "SmartTrader.UpdateOrder: id {0} smartId {1} type {2} direction {3} price {4} volume {5} balance {6} time {7} security {8} state {9}".Put(orderId, smartOrderId, type, direction, price, volume, balance, time, securityId, state));

			Action evtOrder = () => ProcessSecurityAction(null, securityId, security =>
			{
				Order changedOrder = null;

				if (state.IsReject())
				{
					// заявка была ранее зарегистрирована через SmartTrader
					if (_smartIdOrders.ContainsKey(smartOrderId))
					{
						// замечены SystemCancel приходящие в процессе Move после которых приходит Active
						if (state != StOrder_State.StOrder_State_SystemCancel)
						{
							var order = GetOrderBySmartId(smartOrderId);
							RaiseOrderFailed(order, new InvalidOperationException("Заявка с транзакцией {0} не была принята системой.".Put(order.TransactionId)));
						}
					}

					return;
				}

				var realOrderId = type == StOrder_Type.StOrder_Type_Stop || type == StOrder_Type.StOrder_Type_StopLimit
				                  	? smartOrderId.To<long>()
				                  	: orderId;

				if (realOrderId == 0)
				{
					var orders = _smartIdOrders[smartOrderId];

					// заявка не принята биржей, но принята сервером SmartCOM
					if (orders.Count > 0)
					{
						orders.ForEach(o =>
						{
							o.Status = OrderStatus.ReceiveByServer;
							//o.State = OrderStates.Done;
							//o.CancelTime = time;

							o.SetSmartStatus(status);
							o.RaiseExtensionInfoChanged();
						});

						// http://stocksharp.com/forum/yaf_postst679_-3-0-14--Pierierieghistratsiia-zaiavok-Smart.aspx
						// OrdersChanged вызывается до NewOrders
						//RaiseOrdersChanged(orders);
					}

					return;
				}

				if (state == StOrder_State.StOrder_State_Cancel && !Orders.Any(o => o.Id == realOrderId))
					return;

				var isNew = false;

				GetOrder(security, realOrderId, key =>
				{
					isNew = true;

					return _smartIdOrders.SyncGet(d =>
					{
						var orders = d[smartOrderId];

						// Log(() => "realOrderId={0},cnt={1},".Put(realOrderId, orders.Count));
						if (orders.Count == 1)
						{
							var newOrder = orders.First();
							newOrder.Id = key;
							return newOrder;
						}
						else if (orders.Count > 1)
						{
							var last = orders.Last();
							last.Id = key;

							if (last.State == OrderStates.None)
								last.State = OrderStates.Active;

							var prev = orders.ElementAt(orders.Count - 2);
							prev.LastChangeTime = security.GetMarketTime();
							prev.State = OrderStates.Done;

							changedOrder = prev;
							return last;
						}
						else
						{
							var newOrder = EntityFactory.CreateOrder(security, key);

							newOrder.Portfolio = GetPortfolio(portfolio);
							newOrder.Direction = (OrderDirections)direction;
							newOrder.Price = price;
							newOrder.Volume = volume;
							newOrder.Type = type.ToOrderType();

							if (newOrder.ExtensionInfo == null)
								newOrder.ExtensionInfo = new Dictionary<object, object>();

							newOrder.SetSmartId(smartOrderId);
							newOrder.ExpiryDate = isOneDay ? security.GetMarketTime().Date : DateTime.MaxValue;

							if (newOrder.Type == OrderTypes.Conditional)
							{
								newOrder.Condition = new SmartOrderCondition
								{
									StopPrice = stop
								};
							}

							d.Add(smartOrderId, newOrder);
							return newOrder;
						}
					});
				},
				order =>
				{
					using (order.BeginUpdate())
					{
						if (order.GetSmartId() == null)
							order.SetSmartId(smartOrderId);


						// 
						// если SmartCOM послал события неупорядоченно (например, сначала исполнена, затем снова открыта и т.д.)
						// http://stocksharp.com/forum/yaf_postsm5553_-BLOCKER---S-3-0beta--trieidy-nie-rieghistriruiutsia-pod-statieghiiei.aspx#post5553
						//
						switch (order.State)
						{
							case OrderStates.Active:
								if (state == StOrder_State.StOrder_State_Submited || state == StOrder_State.StOrder_State_Pending)
									return false;
								break;
						}

						if (order.State != OrderStates.None && order.Balance < balance)
							return false;

						switch (state)
						{
							case StOrder_State.StOrder_State_ContragentReject:
								order.Status = OrderStatus.NotAcceptedByManager;
								order.State = OrderStates.Failed;
								break;
							// Принята ТС
							case StOrder_State.StOrder_State_Submited:
								order.Status = OrderStatus.SentToServer;
								break;
							// Зарегистрирована в ТС
							case StOrder_State.StOrder_State_Pending:
								order.Status = OrderStatus.ReceiveByServer;

								if (order.Type == OrderTypes.Conditional)
									order.State = OrderStates.Active;

								break;
							// Выведена на рынок
							case StOrder_State.StOrder_State_Open:
								order.Status = OrderStatus.Accepted;
								order.Time = time;

								if (order.Type == OrderTypes.Conditional && orderId != 0)
									order.State = OrderStates.Done;
								else
									order.State = OrderStates.Active;
								break;
							// Снята по окончанию торгового дня
							case StOrder_State.StOrder_State_Expired:
								order.State = OrderStates.Done;
								break;
							// Отменёна
							case StOrder_State.StOrder_State_Cancel:
								order.State = OrderStates.Done;
								order.LastChangeTime = time;
								break;
							// Исполнена
							case StOrder_State.StOrder_State_Filled:
								order.State = OrderStates.Done;
								break;
							// Частично исполнена
							case StOrder_State.StOrder_State_Partial:
								if (order.State == OrderStates.None)
									order.State = OrderStates.Active;
								break;
							// Отклонена биржей
							case
								StOrder_State.StOrder_State_ContragentCancel:
								order.Status = OrderStatus.CanceledByManager;
								order.State = OrderStates.Failed;
								break;
							// Отклонена биржей
							case StOrder_State.StOrder_State_SystemReject:
								order.Status = OrderStatus.NotDone;
								order.State = OrderStates.Failed;
								break;
							// Отклонена биржей
							case StOrder_State.StOrder_State_SystemCancel:
								order.Status = OrderStatus.GateError;
								order.State = OrderStates.Failed;
								break;
							default:
								throw new ArgumentOutOfRangeException();
						}

						order.SetSmartStatus(status);
						order.RaiseExtensionInfoChanged();

						order.Balance = balance;

						if (order.Type == OrderTypes.Conditional && orderId != 0)
						{
							order.State = OrderStates.Done;
							AddDerivedOrder(security, orderId, order, (s, o) => s.DerivedOrder = o);
						}

						if (state == StOrder_State.StOrder_State_Filled)
						{
							// Фикс на случай когда послали ReRegister, но переставляемая заявка зафиллена
							// до того как новая выставлена (и по новой в этом случае не будет event-ов)
							// В этом случае, ищем среди заявок с этим smartId заявку со статусом None и файлим ee
							_smartIdOrders.SyncDo(d =>
							{
								var orders = d[smartOrderId];

								orders.Where(ord => ord.State == OrderStates.None).ForEach(ord =>
								{
									this.AddOrderErrorLog(ord, "Fail On Fill", () => "sid=" + ord.GetSmartId());
									ord.Status = OrderStatus.NotDone;
									ord.State = OrderStates.Failed;

									RaiseOrderFailed(ord,
										new InvalidOperationException("Перестановка заявки {0} не возможно в силу того что удовлетворена исходная заявка."
																							.Put(ord.TransactionId)));
								});
							});
						}
					}

					//_isNeedAutoSave = true;

					if (isNew)
					{
						if (order.Type != OrderTypes.Conditional && changedOrder != null)
							RaiseOrderChanged(changedOrder);
					}

					return true;
				});
			});

			var canRaise = _suspendedOrderEvents.SyncGet(d =>
			{
				if (_synchronizeOrderEvents && !_smartIdOrders.ContainsKey(smartOrderId))
				{
					d[smartOrderId] = evtOrder;
					return false;
				}
				
				return true;
			});

			if (canRaise)
				evtOrder();
		}


		private void OnQuoteChanged(string securityId, int row, int rowCount, decimal bidPrice, decimal bidVolume, decimal askPrice, decimal askVolume)
		{
			ProcessSecurityAction(null, securityId, security =>
			{
				//Debug.Write("Row = " + row + " Bid = " + bidPrice + " BidVolume = " + bidVolume + " Ask = " + askPrice + " AskVolume = " + askVolume);

				var tempDepth = _tempDepths.SafeAdd(security, key => new MarketDepth(key));

				if (row == 0)
					tempDepth.Update(new Quote[0], security.GetMarketTime());

				try
				{
					if (tempDepth.Depth > rowCount)
						tempDepth.Decrease(rowCount);

					if (bidPrice != 0)
					{
						tempDepth.AddQuote(new Quote
						{
							Security = security,
							OrderDirection = OrderDirections.Buy,
							Price = bidPrice,
							Volume = bidVolume,
						});
					}

					if (askPrice != 0)
					{
						tempDepth.AddQuote(new Quote
						{
							Security = security,
							OrderDirection = OrderDirections.Sell,
							Price = askPrice,
							Volume = askVolume,
						});
					}
				}
				finally
				{
					if ((row + 1) == rowCount)
					{
						RaiseMarketDepthChanged(GetMarketDepth(security).Update(tempDepth.Bids, tempDepth.Asks, true, tempDepth.LastChangeTime).InitLatency());
					}
				}
			}, true);
		}

		private void OnConnected()
		{
			RaiseConnected();
		}

		private void OnDisconnected(Exception error)
		{
			IsExportStarted = false;

			if (error == null)
				RaiseDisconnected();
			else
				RaiseConnectionError(error);
		}

		private static string GetSmartId(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.ExtensionInfo == null)
				throw new ArgumentException("Расширенная информация для заявки не инициализирована.", "order");

			var smartId = order.GetSmartId();

			if (smartId == null)
				throw new ArgumentException("Заявка не содержит идентификатор Smart.");

			return smartId;
		}

		private void AssociateSmartOrderId(Order order, string smartOrderId)
		{
			order.SetSmartId(smartOrderId);
			order.RaiseExtensionInfoChanged();
			_smartIdOrders.Add(smartOrderId, order);
		}

		private IEnumerable<Order> GetOrdersBySmartId(string smartOrderId)
		{
			var orders = _smartIdOrders[smartOrderId];

			if (orders == null)
				throw new InvalidOperationException("Не найдена ни одна заявка с SmartCOM номером {0}.".Put(smartOrderId));

			return orders;
		}

		private Order GetOrderBySmartId(string smartOrderId)
		{
			return GetOrdersBySmartId(smartOrderId).Last();
		}

		/// <summary>
		/// Сохранить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public override void Save(SettingsStorage storage)
		{
			base.Save(storage);

			storage.SetValue("Login", Login);
			storage.SetValue("Password", Password);
			storage.SetValue("Address", Address.To<string>());
			storage.SetValue("TradesFromSessionStart", TradesFromSessionStart);
		}

		/// <summary>
		/// Загрузить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public override void Load(SettingsStorage storage)
		{
			base.Load(storage);

			Login = storage.GetValue<string>("Login");
			Password = storage.GetValue<string>("Password");
			Address = storage.GetValue<IPEndPoint>("Address");
			TradesFromSessionStart = storage.GetValue<bool>("TradesFromSessionStart");
		}

		/// <summary>
		/// Освободить занятые ресурсы. В частности, отключиться от сервера SmartCOM.
		/// </summary>
		protected override void DisposeManaged()
		{
			NewPortfolios -= OnNewPortfolios;

			_realTimeCandlesTimer.Dispose();

			//IsAutoSaveOrders = false;

			try
			{
				if (IsConnected)
					StopExport();
			}
			catch (Exception ex)
			{
				RaiseProcessDataError(ex);
			}

			Wrapper.NewSecurity -= OnNewSecurity;
			Wrapper.SecurityChanged -= OnSecurityChanged;
			Wrapper.NewPortfolio -= OnNewPortfolio;
			Wrapper.PortfolioChanged -= OnPortfolioChanged;
			Wrapper.PositionChanged -= OnPositionChanged;
			Wrapper.QuoteChanged -= OnQuoteChanged;
			Wrapper.NewTrade -= OnNewTrade;
			Wrapper.NewMyTrade -= OnNewMyTrade;
			Wrapper.NewHistoryTrade -= OnNewHistoryTrade;
			Wrapper.NewBar -= OnNewBar;
			Wrapper.NewOrder -= OnNewOrder;
			Wrapper.OrderFailed -= OnOrderFailed;
			Wrapper.OrderCancelFailed -= OnOrderCancelFailed;
			Wrapper.OrderChanged -= OnOrderChanged;
			Wrapper.Connected -= OnConnected;
			Wrapper.Disconnected -= OnDisconnected;

			Wrapper.OrderReRegisterFailed -= OnOrderReRegisterFailed;
			Wrapper.OrderReRegistered -= OnOrderReRegistered;

			base.DisposeManaged();

			Wrapper.Dispose();
		}
	}
}